<?php

namespace app\backend\library;

use app\backend\ApiBaseController;
use Exception;
use think\exception\ValidateException;
use think\response\Json;

abstract class DataController extends ApiBaseController
{
    //模型类名（子类必须定义)
    protected $mdl;
    //允许排序字段集合
    protected $allowSortFields = [];
    //允许查询字段集合
    protected $allowSearchFields = [];
    //每页默认数量
    protected $defaultPageSize = 10;
    //最大每页数量
    protected $maxPageSize = 100;
    //关联配置（子类定义）
    protected $relations = [];
    //关联表别名映射（用于 JOIN 查询）
    protected $relationAlias = [];
    //允许主表字段集合
    protected $allowMainFields = ['*'];
    //排除主表字段集合
    protected $excludeMainFields = [];

    /**
     * 列表查询
     * @return Json
     */
    public function getList()
    {
        try{
            //参数验证
            $params = $this->validateParams();

            //模型验证
            if(!$this->mdl) {
                throw new Exception('模型类未定义');
            }

            //初始化查询
            $query = $this->initQuery();

            //应用搜索条件
            $this->applySearch($query, $params);

            //应用排序
            $this->applySort($query, $params);

            //分页查询
            $paginator = $this->executePaginate($query, $params);

            //返回结果
            return $this->successPaginate($paginator);
        }catch (ValidateException $e){
            return $this->jsonError($e->getError());
        }catch (Exception $e){
            return $this->jsonError($e->getMessage(), $e->getFile(), $e->getLine());
        }
    }

    /**
     * 保存数据
     * @return Json
     */
    public function save()
    {
        try {
            $params = $this->request->param();

            $this->_saveBeforeHook($params);

            $this->mdl::saveData($params);

            $this->_saveAfterHook($params);

            return $this->jsonSuccess();
        } catch (ValidateException $e) {
            return $this->jsonError($e->getError());
        } catch (Exception $e) {
            return $this->jsonError($e->getMessage());
        }
    }

    /**
     * 删除数据
     * @return Json
     */
    public function delete()
    {
        try {
            $params = $this->request->param();

            $this->_deleteBeforeHook($params);

            $this->mdl::delData($params);

            $this->_deleteAfterHook($params);

            return $this->jsonSuccess();
        } catch (Exception $e) {
            return $this->jsonError($e->getMessage());
        }
    }

    /**
     * 参数验证
     * @return array|mixed
     */
    protected function validateParams()
    {
        //参数获取
        $params = $this->request->param();

        //参数处理
        $this->_doParamsHook($params);

        $data = $this->request->only([
            'page' => 1,
            'size' => $this->defaultPageSize,
            'sort' => [],
            'search' => '',
            'filters' => []
        ], 'get');

        // 基本验证规则
        $rules = [
            'page'      => 'integer|min:1',
            'size'  => 'integer|between:1,' . $this->maxPageSize,
            'sort'      => 'array|max:5',//仅限最多 5 个排序字段
            'search'    => 'max:100',
            'filters'   => 'array'
        ];

        // 自定义验证场景（子类可扩展）
        $this->validate($data, $rules);

        // 解析排序参数
        $data['sortRules'] = $this->parseSortParam($data['sort']);

        return $data;
    }

    protected function parseSortParam($sortParams)
    {
        $sortRules = [];

        if (!empty($sortParams)) {
            foreach ($sortParams as $field => $order) {
                // 验证排序方向
                $order = strtolower($order);
                if (!in_array($order, ['asc', 'desc'])) {
                    continue;
                }

                $sortRules[] = [
                    'field' => $field,
                    'order' => $order
                ];
            }
        }

        // 默认排序
        if (empty($sortRules)) {
            $sortRules[] = [
                'field' => 'id',
                'order' => 'desc'
            ];
        }

        return $sortRules;
    }

    /**
     * 初始化查询（处理关联）
     * @return mixed
     */
    protected function initQuery()
    {
        $query = (new $this->mdl)->newQuery();

        if(!empty($this->relations)) {
            foreach ($this->relations as $relation => $config) {
                if ($config['type'] === 'with') {
                    // 预加载关联
                    $query->with($relation);
                } elseif ($config['type'] === 'join') {
                    // JOIN关联
                    $alias = $config['alias'] ?? $relation;
                    $this->relationAlias[$relation] = $alias;

                    $query->join(
                        [$config['table'] => $alias],
                        $config['on'],
                        $config['join_type'] ?? 'LEFT'
                    );
                }
            }
        }

        return $query;
    }

    /**
     * 查询处理
     * @param $query
     * @param $params
     * @return void
     */
    protected function applySearch($query, $params)
    {
        // 全局搜索（OR 条件）
        if (!nullEmpty($params['search'])) {
            $query->where(function($q) use ($params) {
                foreach ($this->allowSearchFields as $field) {
                    $this->applyFieldSearch($q, $field, $params['search']);
                }
            });
        }

        // 字段过滤（AND 条件）
        if (!empty($params['filters']) && is_array($params['filters'])) {
            foreach ($params['filters'] as $field => $value) {
                if (!nullEmpty($value) && in_array($field, $this->allowSearchFields)) {
                    $this->applyFieldFilter($query, $field, $value);
                }
            }
        }
    }

    /**
     * 应用字段搜索（支持不同匹配模式）
     * @param $query
     * @param $field
     * @param $value
     * @return void
     */
    protected function applyFieldSearch($query, $field, $value)
    {
        //处理关联字段
        if(strpos($field, '.') !== false) {
            list($relation, $fieldName) = explode('.', $field, 2);
            $this->applyRelationSearch($query, $relation, $fieldName, $value);
            return;
        }

        //根据字段类型应用不同搜索逻辑
        $this->applyFieldSearchType($query, $field, $value);
    }

    /**
     * 应用关联表搜索
     */
    protected function applyRelationSearch($query, $relation, $field, $value)
    {
        // 检查关联配置
        if (!isset($this->relations[$relation])) return;

        $config = $this->relations[$relation];

        if ($config['type'] === 'with') {
            // 使用关联模型搜索
            $query->hasWhere($relation, function($q) use ($field, $value, $relation) {
                $this->applyFieldSearchType($q, "{$relation}.{$field}", $value);
            });
        } elseif ($config['type'] === 'join') {
            // 使用 JOIN 条件搜索
            $alias = $this->relationAlias[$relation] ?? $relation;
            $this->applyFieldSearchType($query, "{$alias}.{$field}", $value);
        }
    }

    /**
     * 根据字段类型应用不同搜索逻辑
     * @param $query
     * @param $field
     * @param $value
     * @return void
     */
    protected function applyFieldSearchType($query, $field, $value)
    {
        if(strpos($field, '.') !== false) {
            list(, $pureField) = explode('.', $field, 2);
            $fieldAlias = $field;
            $fieldType = $this->getFieldType($pureField);
        }else{
            $fieldType = $this->getFieldType($field);
            $fieldAlias = $this->mainFieldAlias($field);
        }

        switch ($fieldType) {
            case 'string':
                $query->whereOr($fieldAlias, 'like', "%{$value}%");
                break;
            case 'number':
                if(is_numeric($value)) {
                    $query->whereOr($fieldAlias, $value);
                }
                break;
            case 'date':
                if($date = strtotime($value)) {
                    $query->whereOr($fieldAlias, date('Y-m-d', $date));
                }
                break;
            case 'enum':
                $query->whereOr($fieldAlias, $value);
                break;
            default:
                $query->where($fieldAlias, 'like', "%{$value}%");
        }
    }

    /**
     * 应用关联表过滤
     * @param $query
     * @param $field
     * @param $value
     * @return void
     */
    protected function applyFieldFilter($query, $field, $value)
    {
        //处理关联字段
        if(strpos($field, '.') !== false) {
            list($relation, $fieldName) = explode('.', $field, 2);
            $this->applyRelationFilter($query, $relation, $fieldName, $value);
            return;
        }

        $this->applyFieldFilterType($query, $field, $value);
    }

    /**
     * 应用关联表过滤
     */
    protected function applyRelationFilter($query, $relation, $field, $value)
    {
        // 检查关联配置
        if (!isset($this->relations[$relation])) return;

        $config = $this->relations[$relation];

        if ($config['type'] === 'with') {
            // 使用关联模型过滤
            $query->hasWhere($relation, function($q) use ($field, $value, $relation) {
                $this->applyFieldFilterType($q, "{$relation}.{$field}", $value);
            });
        } elseif ($config['type'] === 'join') {
            // 使用 JOIN 条件过滤
            $alias = $this->relationAlias[$relation] ?? $relation;
            $this->applyFieldFilterType($query, "{$alias}.{$field}", $value);
        }
    }

    /**
     * 根据字段类型应用不同搜索逻辑
     * @param $query
     * @param $field
     * @param $value
     * @return void
     */
    protected function applyFieldFilterType($query, $field, $value)
    {
        //关联字段处理
        if(strpos($field, '.') !== false) {
            list(, $pureField) = explode('.', $field, 2);
            $fieldAlias = $field;
            $fieldType = $this->getFieldType($pureField);
        }else{
            $fieldType = $this->getFieldType($field);
            $fieldAlias = $this->mainFieldAlias($field);
        }

        switch($fieldType) {
            case 'string':
                //支持精准匹配和模糊匹配
                if(strpos($value, '%') !== false) {
                    $query->where($fieldAlias, 'like', $value);
                }else{
                    $query->where($fieldAlias, $value);
                }
                break;
            case 'number':
                //支持范围查询 [min,max]
                if(is_array($value) && count($value) == 2) {
                    $query->whereBetween($fieldAlias, $value);
                }else{
                    $query->where($fieldAlias, $value);
                }
                break;
            case 'date':
                //支持日期范围
                if(is_array($value) && count($value) == 2) {
                    $query->whereBetween($fieldAlias, $value);
                }else{
                    $query->whereDate($fieldAlias, $value);
                }
                break;
            case 'enum':
                //支持多选
                if(is_array($value)) {
                    $query->whereIn($fieldAlias, $value);
                }else{
                    $query->where($fieldAlias, $value);
                }
                break;
            default:
                $query->where($fieldAlias, $value);
        }
    }

    /**
     * 排序处理
     * @param $query
     * @param $params
     * @return void
     */
    protected function applySort($query, $params)
    {
        $sortRules = $params['sortRules'];

        foreach($sortRules as $sort) {
            $field = $sort['field'];
            $order = $sort['order'];

            //检查排序字段是否允许
            if(!$this->isAllowSortField($field)) {
                continue;
            }

            //处理关联字段
            if(strpos($field, '.') !== false) {
                $this->applyRelationSort($query, $field, $order);
            }else{
                //主表字段排序
                $query->order($this->mainFieldAlias($field), $order);
            }
        }
    }

    /**
     * 排序字段白名单
     * @param $field
     * @return bool
     */
    protected function isAllowSortField($field)
    {
        if(in_array($field, $this->allowSortFields)) {
            return true;
        }

        return false;
    }

    /**
     * 关联字段排序
     * @param $query
     * @param $field
     * @param $order
     * @return void
     */
    protected function applyRelationSort($query, $field, $order) {
        list($relation, $fieldName) = explode('.', $field, 2);

        if(!isset($this->relations[$relation])) return;

        //验证字段是否存在于关联配置中
        $config = $this->relations[$relation];
        if(isset($config['fields']) && !in_array($fieldName, $config['fields'])) {
            return;
        }

        $query->with([$relation => function($query) use ($fieldName, $order) {
            $query->order($fieldName, $order);
        }]);
    }

    /**
     * 分页查询
     * @param $query
     * @param $params
     * @return mixed
     */
    protected function executePaginate($query, $params)
    {
        //获取查询字段
        $field = $this->mainFieldList();

        // 处理关联表字段选择
        if (!empty($this->relations)) {
            foreach ($this->relations as $relation => $config) {
                if ($config['type'] === 'join') {
                    $alias = $this->relationAlias[$relation] ?? $relation;
                    if (!empty($config['fields'])) {
                        foreach ($config['fields'] as $f) {
                            $field[] = "{$alias}.{$f} as {$relation}_{$f}";
                        }
                    }
                }elseif($config['type'] == 'with') {
                    if(!empty($config['fields'])) {
                        $query->with([$relation => function ($q) use ($config) {
                            $q->field($config['fields']);
                        }]);
                    }
                }
            }
        }

        return $query->field($field)->paginate([
            'page' => $params['page'],
            'list_rows' => $params['size'],
            'query' => $this->request->get()
        ]);
    }

    /**
     * 分页查询结果处理
     * @param $paginator
     * @return Json
     */
    protected function successPaginate($paginator)
    {
        $items = $paginator->items();

        // 处理关联数据格式
        if (!empty($this->relations)) {
            foreach ($items as &$item) {
                foreach ($this->relations as $relation => $config) {
                    if ($config['type'] === 'join') {
                        $prefix = $relation . '_';
                        $relationData = [];

                        foreach ($item->getData() as $key => $value) {
                            if (strpos($key, $prefix) === 0) {
                                $field = substr($key, strlen($prefix));
                                $relationData[$field] = $value;
                                unset($item->$key);
                            }
                        }

                        $item->setAttr($relation, (object)$relationData);
                    }
                }
            }
        }

        //数据处理
        $this->_printDataHook($items);

        return $this->jsonSuccess('success', [
            'list' => $items,
            'pagination' => [
                'total' => $paginator->total(),
                'current_page' => $paginator->currentPage(),
                'per_page' => $paginator->listRows(),
                'last_page' => $paginator->lastPage()
            ]
        ]);
    }

    /**
     * 获取字段类型（需在模型中定义）
     * @param $field
     * @return string
     */
    protected function getFieldType($field)
    {
        //从模型中获取字段类型定义
        if($this->mdl && property_exists($this->mdl, 'fieldTypes')) {
            return $this->mdl::$fieldTypes[$field] ?? 'string';
        }

        return 'string';
    }

    /**
     * 获取主表字段列表
     * @return array|string[]
     */
    protected function mainFieldList()
    {
        $fieldList = [];

        $tableFields = $this->mdl::getTableFields();

        //允许字段
        if(!empty($this->allowMainFields)) {
            foreach ($this->allowMainFields as $field) {
                //如果是 *
                if($field === '*') {
                    $fieldList = array_combine($tableFields, $tableFields);
                    break;
                }

                //允许字段
                if(in_array($field, $tableFields)) {
                    $fieldList[$field] = $field;
                }
            }
        }

        //排序字段
        if(!empty($this->excludeMainFields)) {
            foreach ($this->excludeMainFields as $field) {
                if(in_array($field, $tableFields) && !empty($fieldList[$field])) {
                    unset($fieldList[$field]);
                }
            }
        }

        //字段处理
        if(!empty($fieldList)) {
            $fieldList = array_values(array_filter($fieldList));
            if(!empty($fieldList)) {
                //如果存在字段与表格中的一致，则使用 *
                if(!array_diff($tableFields, $fieldList)) {
                    $fieldList = ['*'];
                }


                //添加字段别名
                $fieldList = array_map(function ($field) {
                    return $this->mainFieldAlias($field);
                }, $fieldList);
            }
        }

        return $fieldList;
    }

    /**
     * 主表字段别名
     * @param $field
     * @param $alias
     * @return string
     */
    protected function mainFieldAlias($field, $alias = '')
    {
        //关联字段
        if(false !== strpos($field, '.')) {
            return $field;
        }

        if(!$alias) $alias = $this->mdl::getTable();

        return implode('.', [$alias, $field]);
    }

    /**
     * 数据输入前置处理
     * @param $params
     * @return void
     */
    protected function _doParamsHook(&$params) {

    }

    /**
     * 数据输出前置处理
     * @param $paginator
     * @return void
     */
    protected function _printDataHook(&$items)
    {

    }

    /**
     * 保存数据前置操作
     * @param $params
     * @return void
     */
    protected function _saveBeforeHook(&$params)
    {

    }

    /**
     * 保存数据后置操作
     * @param $params
     * @return void
     */
    protected function  _saveAfterHook(&$params)
    {

    }

    /**
     * 删除数据前置操作
     * @param $params
     * @return void
     */
    protected function _deleteBeforeHook(&$params)
    {

    }

    /**
     * 删除数据后置操作
     * @param $params
     * @return void
     */
    protected function _deleteAfterHook(&$params)
    {

    }

}